package ldh.im.fxgui.handle;

import dev.onvoid.webrtc.*;
import dev.onvoid.webrtc.media.MediaDevices;
import dev.onvoid.webrtc.media.MediaStreamTrack;
import dev.onvoid.webrtc.media.audio.AudioOptions;
import dev.onvoid.webrtc.media.audio.AudioTrack;
import dev.onvoid.webrtc.media.audio.AudioTrackSource;
import dev.onvoid.webrtc.media.video.VideoCaptureCapability;
import dev.onvoid.webrtc.media.video.VideoDevice;
import dev.onvoid.webrtc.media.video.VideoDeviceSource;
import dev.onvoid.webrtc.media.video.VideoTrack;
import io.netty.channel.Channel;
import ldh.im.ImConfig;
import ldh.im.dto.base.OkResp;
import ldh.im.dto.business.PingReq;
import ldh.im.dto.business.user.LoginResp;
import ldh.im.dto.business.webrtc.*;
import ldh.im.dto.business.webrtc.mesh.WebRtcP2pCandidateReq;
import ldh.im.dto.business.webrtc.mesh.WebRtcP2pSdpReq;
import ldh.im.dto.enums.ChatType;
import ldh.im.dto.enums.SdpType;
import ldh.im.exception.BusinessException;
import ldh.im.fxbase.component.VideoView;
import ldh.im.fxbase.rpc.SignalCallBack;
import ldh.im.fxbase.rpc.router.SignalConsumerRouter;
import ldh.im.fxbase.rpc.service.RpcSignalService;
import ldh.im.fxbase.service.ChannelService;
import ldh.im.fxbase.util.CloseFactory;
import ldh.im.fxbase.util.FxThreadUtil;
import ldh.im.fxbase.util.RpcUtil;
import ldh.im.fxbase.util.ThreadPoolUtil;
import ldh.im.util.ConfigUtil;
import ldh.rpc.util.JsonUtil;
import ldh.rpc.TerminalType;
import ldh.rpc.cache.CacheService;
import ldh.rpc.client.RpcClientBootstrap;
import ldh.rpc.dto.PongResp;
import ldh.rpc.exception.RpcException;
import ldh.rpc.listener.ChannelEventType;
import ldh.rpc.listener.ChannelListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 功能如下：
 * a, 信令服务器的回调通知：
 *  1，邀请人入房
 *  2，接受对方的sdp
 *  3，接受对方的candidate
 *  4，接受对方退房
 *
 * b, 对信令服务器发出指令
 *  1，登录信令服务器
 *  2，创建房间
 *  3，入房
 *  4，退房
 *
 * c，基础操作
 *  1，打开本地视频
 *  2，关闭本地视频
 *  3，信令服务器是否连接指令
 *
 */
public class WebRtcClient implements SignalCallBack, Closeable {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebRtcClient.class);

    public static final double videoWidth = 150;
    public static final double videoHeight = 200;
    public static final int MAX_USER_LIMIT = 7;       // 视频群聊最多7人

    private RpcClientBootstrap rpcClientBootstrap;
    private RpcSignalService rpcSignalService = null;
    private String roomName;
    private boolean isAudio;
    private double screenWidth;
    private double screenHeight;

    private Map<String, PeerConnectionData> peerConnectionMap = new ConcurrentHashMap<>();

    private WebRtcUiEvent webRtcUiEvent;

    private RTCConfiguration config = new RTCConfiguration();
    private PeerConnectionFactory factory = null;

    private VideoDevice videoDevice;
    private VideoCaptureCapability captureCapability;
    private VideoDeviceSource videoSource = null;
    private AudioTrack audioTrack;
    private VideoTrack videoTrack;
    private boolean captureVideoIsRunning = false; // VideoDeviceSource 是否Running
    private WebRtcRoomInfo webRtcRoomInfo = null;
    private String loginUserId;

    private LocalDateTime lastedActiveSignalTime;  // 最近活动时间，如果超长时间未更新，连接中断

    public WebRtcClient(WebRtcUiEvent webRtcUiEvent, String loginUserId, String roomName, boolean isAudio,
                        double screenWidth, double screenHeight) {
        this.webRtcUiEvent = webRtcUiEvent;
        this.loginUserId = loginUserId;
        this.roomName = roomName;
        this.isAudio = isAudio;
        this.screenWidth = screenWidth;
        this.screenHeight = screenHeight;

        CloseFactory.getInstance().addCloseable("WebRtcClient_" + this.hashCode(), this);
    }

    public void start() {
        // 连接WebRtc信令服务器
        initRpcClientBootstrap();
        if (roomName != null && !isAudio && !captureVideoIsRunning) {
            LOGGER.info("start open video");
            // 如果是视频接受者，开启本地视频
            initConnectFactory();
            openVideo(0, 0);
        }
    }

    //  获取webRtc中已经建立连接的用户
    public Set<String> getConnectedUsers() {
        return peerConnectionMap.keySet().stream().collect(Collectors.toSet());
    }

    // 登录
    public LoginResp login() throws RpcException {
        CacheService cacheService = RpcUtil.getRpcBootstrap().getRpcConfig().getCacheService();
        Object userId = cacheService.getCache(ImConfig.LOGIN_USER_ID);
        Object tenantCode = cacheService.getCache(ImConfig.TENANT_ID);
        Object authToken = cacheService.getCache(ImConfig.AUTH_TOKEN);
        if (userId != null) {
            WebRtcLoginReq webRtcLoginReq = new WebRtcLoginReq();
            webRtcLoginReq.setTerminalType(TerminalType.pc);
            webRtcLoginReq.setTenantCode(tenantCode.toString());
            webRtcLoginReq.setAuthToken(authToken.toString());
            RpcSignalService rpcSignalService = rpcClientBootstrap.getRpcConfig().getClientBean(RpcSignalService.class);
            LoginResp loginResp = rpcSignalService.login(webRtcLoginReq);
            this.lastedActiveSignalTime = LocalDateTime.now();
            return loginResp;
        }
        throw new BusinessException("userId为空");
    }

    // 创建房间
    public String createRoom(boolean isAudio) throws RpcException {
        WebRtcCreateRoomReq webRtcCreateRoomReq = new WebRtcCreateRoomReq();
        webRtcCreateRoomReq.setChatType(isAudio ? ChatType.audio : ChatType.video);
        webRtcCreateRoomReq.setTerminalType(TerminalType.pc);
        webRtcCreateRoomReq.setWidth(screenWidth);
        webRtcCreateRoomReq.setHeight(screenHeight);
        RpcSignalService rpcSignalService = rpcClientBootstrap.getRpcConfig().getClientBean(RpcSignalService.class);
        OkResp resp = rpcSignalService.roomCreate(webRtcCreateRoomReq);
        LOGGER.info("room create success, roomName: {}", resp.getOtherInfo());
        this.roomName = resp.getOtherInfo();

        this.webRtcRoomInfo = new WebRtcRoomInfo();
        webRtcRoomInfo.setName(this.roomName);
        CacheService cacheService = RpcUtil.getRpcBootstrap().getRpcConfig().getCacheService();
        Object userId = cacheService.getCache(ImConfig.LOGIN_USER_ID);
        WebRtcUserInfo userInfo = new WebRtcUserInfo(userId.toString(), TerminalType.pc, screenWidth, screenHeight);
        List<WebRtcUserInfo> userInfoList = new ArrayList<>();
        userInfoList.add(userInfo);
        webRtcRoomInfo.setUserInfoList(userInfoList);

        return this.roomName;
    }

    // 进入房间
    public WebRtcRoomInfo joinRoom(String roomName) throws RpcException {
        WebRtcJoinRoomReq joinRoomReq = new WebRtcJoinRoomReq();
        joinRoomReq.setRoomName(roomName);
        joinRoomReq.setTerminalType(TerminalType.pc);
        joinRoomReq.setWidth(screenWidth);
        joinRoomReq.setHeight(screenHeight);
        RpcSignalService rpcSignalService = rpcClientBootstrap.getRpcConfig().getClientBean(RpcSignalService.class);
        WebRtcRoomInfo webRtcRoomInfo = rpcSignalService.roomJoin(joinRoomReq);
        this.roomName = roomName;
        this.webRtcRoomInfo = webRtcRoomInfo;
        List<WebRtcUserInfo> userInfoList = webRtcRoomInfo.getUserInfoList();
        for (WebRtcUserInfo userInfo : userInfoList) {
            if (userInfo.getUserId().equals(loginUserId)) continue;
            if (userInfo.getTerminalType() == TerminalType.android) {
                this.createOffer(userInfo.getUserId(), joinRoomReq.getWidth(), joinRoomReq.getHeight());
            }
        }
        LOGGER.info("room join success, webRtcRoomInfo: {}", JsonUtil.toJson(webRtcRoomInfo));
        return webRtcRoomInfo;
    }

    // 退出房间
    public void exitRoom(String roomName) throws RpcException {
        WebRtcExitRoomReq exitRoomReq = new WebRtcExitRoomReq();
        exitRoomReq.setRoomName(roomName);
        RpcSignalService rpcAvService = rpcClientBootstrap.getRpcConfig().getClientBean(RpcSignalService.class);
        OkResp okResp = rpcAvService.roomExit(exitRoomReq);
        LOGGER.info("room exited success, coder: {}", okResp.getCoder());
    }

    @Override
    public PongResp pingToClient(PingReq pingReq) {
        LOGGER.info("signal client receive ping");
        this.lastedActiveSignalTime = LocalDateTime.now();
        return new PongResp();
    }

    @Override
    public OkResp receiveJoinRoomReq(WebRtcJoinRoomReq joinRoomReq) {
        LOGGER.info("signal client receive join room, data: {}", JsonUtil.toJson(joinRoomReq));
        if (this.peerConnectionMap.containsKey(joinRoomReq.getUserId())) { // 此用户已经连接上
            return new OkResp(joinRoomReq.getLocalSeq());
        }
        addUser(joinRoomReq.getUserId(), joinRoomReq.getTerminalType(), joinRoomReq.getWidth(), joinRoomReq.getHeight());
        this.createOffer(joinRoomReq.getUserId(), joinRoomReq.getWidth(), joinRoomReq.getHeight());
        return new OkResp(joinRoomReq.getLocalSeq());
    }

    @Override
    public OkResp receiveExitRoomReq(WebRtcExitRoomReq exitRoomReq) {
        LOGGER.info("signal client receive exit room, data: {}", JsonUtil.toJson(exitRoomReq));
        String userId = exitRoomReq.getUserId();
        PeerConnectionData peerConnectionData = peerConnectionMap.remove(userId);
        if (peerConnectionData != null) {
            peerConnectionData.getPeerConnection().close();
            FxThreadUtil.runLater(()->{
                webRtcUiEvent.exitRoom(exitRoomReq.getUserId(), true);
                removeUser(exitRoomReq.getUserId());
            });
        }
        return new OkResp(exitRoomReq.getLocalSeq());
    }

    @Override
    public OkResp receiveSdp(WebRtcP2pSdpReq sdpReq) {
        LOGGER.info("signal client receive sdp, toUserId: {}, fromUserId: {}", sdpReq.getToUserId(), sdpReq.getUserId());
        String remoteUserId = sdpReq.getUserId();
        if (sdpReq.getSdpType() == SdpType.offer) { // 被连接端
            PeerConnectionData peerConnectionData = peerConnectionMap.get(remoteUserId);
            RTCPeerConnection peerConnection = null;
            if (peerConnectionData == null) {
                initConnectFactory();
                VideoView videoView = new VideoView();
                addVideoView(remoteUserId, videoView);
                peerConnection = createPeerConnection(remoteUserId, videoView);
            }
            RTCSessionDescription sessionDescription = new RTCSessionDescription(RTCSdpType.OFFER, sdpReq.getSdp());
            peerConnection.setRemoteDescription(sessionDescription, new RemoteSessionDescriptionObserver(remoteUserId, peerConnection));
        } else if (sdpReq.getSdpType() == SdpType.answer) { // 发起端
            PeerConnectionData peerConnectionData = peerConnectionMap.get(remoteUserId);
            RTCPeerConnection peerConnection = peerConnectionData.getPeerConnection();
            RTCSessionDescription sessionDescription = new RTCSessionDescription(RTCSdpType.ANSWER, sdpReq.getSdp());
            peerConnection.setRemoteDescription(sessionDescription, new SetSessionDescriptionObserver() {

                @Override
                public void onSuccess() {
                    LOGGER.info("answerSdp setRemoteDescription success");
                }

                @Override
                public void onFailure(String s) {
                    LOGGER.info("answerSdp setRemoteDescription failure");
                }
            });

            Optional<WebRtcUserInfo> remoteUserInfoOptional = getWebRtcUserInfo(remoteUserId);
            if (sdpReq.getSdp().contains("video-orientation") && remoteUserInfoOptional.isPresent()) {
                WebRtcUserInfo webRtcUserInfo = remoteUserInfoOptional.get();
                if (webRtcUserInfo.getTerminalType() == TerminalType.android) {
                    peerConnectionData.getVideoView().setRotate(-90);
                }
            }
        }
        return new OkResp(sdpReq.getLocalSeq());
    }

    @Override
    public OkResp receiveIceCandidateReq(WebRtcP2pCandidateReq webRtcP2pCandidateReq) {
        LOGGER.info("signal client receive candidate, data: {}", JsonUtil.toJson(webRtcP2pCandidateReq));
        String remoteUserId = webRtcP2pCandidateReq.getUserId();
        PeerConnectionData peerConnectionData = peerConnectionMap.get(remoteUserId);
        if (peerConnectionData != null) {
            RTCPeerConnection peerConnection = peerConnectionData.getPeerConnection();
            IceCandidate candidate = webRtcP2pCandidateReq.getIceCandidate();
            peerConnection.addIceCandidate(new RTCIceCandidate(candidate.getSdpMid(), candidate.getSdpMLineIndex(), candidate.getCandidate()));
        }
        return new OkResp(webRtcP2pCandidateReq.getLocalSeq());
    }

    @Override
    public OkResp receiveDelRoomReq(WebRtcDelRoomReq delRoomReq) {
        LOGGER.info("signal client receive del room, data: {}", JsonUtil.toJson(delRoomReq));
        ThreadPoolUtil.submit(()->{
            webRtcUiEvent.exitRoom(delRoomReq.getUserId(), true);
        });
        return new OkResp(delRoomReq.getLocalSeq());
    }

    public void close() {
        if (rpcClientBootstrap != null) {
            rpcClientBootstrap.close();
        }
        closeLocalVideo();
        LOGGER.info("WebRtc.close!!!!!!!!!!!!!!");
    }

    // 调整音量
    public void adjustVolume(int volumeNumber) {

    }

    // 设置语音是否开启
    public void setAudioStatus(boolean audioStatus) {
        LOGGER.info("setAudioStatus {}", audioStatus);
        if (this.audioTrack != null) {
            this.audioTrack.setEnabled(audioStatus);
        }
    }

    // 设置视频是否开启
    public void setVideoStatus(boolean videoStatus) {
        LOGGER.info("setVideoStatus {}", videoStatus);
        if (this.videoTrack != null) {
            this.videoTrack.setEnabled(videoStatus);
        }
    }

    private void closeLocalVideo() {
        peerConnectionMap.values().forEach((peerConnectionData) -> {
            try {
                peerConnectionData.getPeerConnection().close();
            } catch (Exception e) {
                LOGGER.error("close peerConnection error", e);
            }
        });
        if (videoSource != null && captureVideoIsRunning) {
            LOGGER.info("videoSource stop!!!!!!!!!!!!!!");
            videoSource.stop();
            captureVideoIsRunning = false;
        }

        closePeerConnectionFactory();
        LOGGER.info("localVideo close!!!!!!!!!!!!!!");
    }

    private void closePeerConnectionFactory() {
        try {
            if (factory != null) {
                factory.dispose();
            }
            CloseFactory.getInstance().remove("WebRtcClient_" + this.hashCode());
        } catch (Exception error){
            LOGGER.error("[WebRtcClient close factory error]", error);
            CloseFactory.getInstance().remove("WebRtcClient_" + this.hashCode());
        }
    }

    private RTCPeerConnection createPeerConnection(String userId, VideoView videoView2) {
        try {
            SignalPeerConnectionObserver signalPeerConnectionObserver = new SignalPeerConnectionObserver(userId, videoView2);
            RTCPeerConnection peerConnection = factory.createPeerConnection(config, signalPeerConnectionObserver);

            PeerConnectionData peerConnectionData = new PeerConnectionData(peerConnection, videoView2);
            signalPeerConnectionObserver.setPeerConnectionData(peerConnectionData);
            peerConnectionMap.put(userId, peerConnectionData);

            LOGGER.info("[WebRtcClient create peer connection], isAudio: {}", this.isAudio);
            if (isAudio) {
                peerConnection.addTrack(audioTrack, List.of("stream"));
//            setupVideoCodec(peerConnection);
            } else {
                LOGGER.info("isRunning: {}", this.captureVideoIsRunning);
                peerConnection.addTrack(audioTrack, List.of("stream"));
                peerConnection.addTrack(videoTrack, List.of("stream"));
//            setupVideoCodec(peerConnection);
            }

            return peerConnection;
        } catch (Exception e) {
            LOGGER.error("[WebRtcClient createPeerConnection error]", e);
        }
        throw new RuntimeException("createPeerConnection error!");
    }

    private void createOffer(String remoteUserId, double width, double height) {
        initConnectFactory();

        VideoView videoView = new VideoView();
        if (!isAudio && !captureVideoIsRunning) {
            this.openVideo(width, height);
            this.webRtcUiEvent.localMediaCreated();
            this.captureVideoIsRunning = true;
        }
        addVideoView(remoteUserId, videoView);

        PeerConnectionData peerConnectionData = peerConnectionMap.get(remoteUserId);
        if (peerConnectionData == null) {
            RTCPeerConnection peerConnection = createPeerConnection(remoteUserId, videoView);

            peerConnectionMap.put(remoteUserId, new PeerConnectionData(peerConnection, videoView));

            RTCOfferOptions options = new RTCOfferOptions();
            peerConnection.createOffer(options, new OfferSdpCreateSessionDescriptionObserver(remoteUserId, peerConnection));
        }
    }

    private void initConnectFactory() {
        LOGGER.info("init connect factory");
        if (factory != null) {
//            closeLocalVideo();
            return;
        }

        List<VideoDevice> devices = MediaDevices.getVideoCaptureDevices();
        if (devices.size() < 1) {
            throw new RuntimeException("缺少语音设备");
        }

        factory = new PeerConnectionFactory();
        videoSource = new VideoDeviceSource();
        VideoTrack videoTrack = factory.createVideoTrack("CAM", videoSource);

        AudioOptions audioOptions = new AudioOptions();
        AudioTrackSource audioSource = factory.createAudioSource(audioOptions);
        audioTrack = factory.createAudioTrack("audioTrack", audioSource);
    }

    private void openVideo(double videoWidth, double videoHeight) {
        try{
            List<VideoDevice> devices = MediaDevices.getVideoCaptureDevices();
            for (VideoDevice device : devices) {
                List<VideoCaptureCapability> videoCaptureCapabilities = MediaDevices.getVideoCaptureCapabilities(device);
                LOGGER.info("VideoCaptureCapability value: {}", JsonUtil.toJson(videoCaptureCapabilities));
                VideoCaptureCapability captureCapability1 = new VideoCaptureCapability(0, 0, 30);
                if (videoWidth < 1 || videoHeight < 1) {
                    captureCapability1 = getVideoCaptureCapability(videoCaptureCapabilities, 640, 360);
                } else {
                    captureCapability1 = getVideoCaptureCapability(videoCaptureCapabilities, videoWidth, videoHeight);
                }

                if (captureCapability1 != null) {
                    this.captureCapability = captureCapability1;
                    this.videoDevice = device;
                    videoSource.setVideoCaptureDevice(device);
                    videoSource.setVideoCaptureCapability(captureCapability);
                    videoTrack = factory.createVideoTrack("CAM", videoSource);
                    LOGGER.info("videoTrack create success, captureCapability: {}", JsonUtil.toJson(captureCapability1));

                    videoSource.start();
                    this.captureVideoIsRunning = true;
                    break;
                }
            }
        } catch (Exception e) {
            LOGGER.error("[WebRtcClient open video failure], error: {}", e);
            throw new RuntimeException("open video capture failure");
        }
        if (this.captureCapability == null) {
            throw new RuntimeException("get VideoCaptureCapability is null");
        }
    }

    private void addVideoView(String userId, VideoView videoView) {
        FxThreadUtil.runLater(()-> {
            videoView.setPrefSize(videoWidth, videoHeight);
            webRtcUiEvent.receive(userId, videoView);
        });
    }

    private void addUser(String userId, TerminalType terminalType, double screenWidth, double screenHeight) {
        WebRtcUserInfo userInfo = new WebRtcUserInfo(userId, terminalType, screenWidth, screenHeight);
        this.webRtcRoomInfo.getUserInfoList().add(userInfo);
    }

    private void removeUser(String userId) {
        Optional<WebRtcUserInfo> userInfoOptional = getWebRtcUserInfo(userId);
        if (userInfoOptional.isPresent()) {
            this.webRtcRoomInfo.getUserInfoList().remove(userInfoOptional.get());
        }
    }

    private Optional<WebRtcUserInfo> getWebRtcUserInfo(String userId) {
        return this.webRtcRoomInfo.getUserInfoList().stream().filter(u-> u.getUserId().equals(userId)).findFirst();
    }

    private VideoCaptureCapability getVideoCaptureCapability(List<VideoCaptureCapability> videoCaptureCapabilities,
                                                             double width, double height) {
        double minValue = Double.MAX_VALUE;
        int index = 0;
        int position = 0;
        for (VideoCaptureCapability videoCaptureCapability : videoCaptureCapabilities) {
            double value = Math.abs(videoCaptureCapability.height -height);
            if (value < minValue) {
                minValue = value;
                position = index;
            }
            index++;
        }
        if (videoCaptureCapabilities.size() > 0) {
            return videoCaptureCapabilities.get(position);
        }
        return null;
    }

    private void setupVideoCodec(RTCPeerConnection peerConnection) {
         final RTCRtpSender[] senders = peerConnection.getSenders();
         for (RTCRtpSender sender : senders) {
             if (sender.getTrack().getKind().equals("video")) {
                 RTCRtpSendParameters parameters = sender.getParameters();
                 parameters.encodings.forEach((encoding)-> {
                     encoding.maxBitrate = 1500; // 设置最大比特率（单位 kbps）
                     encoding.minBitrate = 600; // 设置最小比特率（单位 kbps）
                     encoding.maxFramerate = 30.0; // 设置最大帧率
                     encoding.scaleResolutionDownBy = 1.0; // 不缩放分辨率，如果要缩放设置为大于1的数
                 });
                 sender.setParameters(parameters);
               }
         }
    }

    private void initRpcClientBootstrap() {
        try {
            String address = ConfigUtil.getString("signal.address", "192.168.1.6");
            int port = ConfigUtil.getInt("signal.port", 9890);
            ChannelService channelService = new ChannelService();

            ChannelListener channelListener = (Channel channel, ChannelEventType channelEventType)-> {

            };

            rpcClientBootstrap = channelService.createWebRtcClientBootstrap(address, port, channelListener);

            SignalConsumerRouter signalConsumerRouter = (SignalConsumerRouter) rpcClientBootstrap.getRpcConfig().getRouter(SignalConsumerRouter.class);
            signalConsumerRouter.addSignalCallBack(this);

            rpcSignalService = rpcClientBootstrap.getRpcConfig().getClientBean(RpcSignalService.class);
        } catch (Exception e) {
            throw new RuntimeException("连接信令服务器失败");
        }
    }

    private class SignalPeerConnectionObserver implements PeerConnectionObserver {

        private String userId;
        private VideoView videoView;
        private PeerConnectionData peerConnectionData;

        public SignalPeerConnectionObserver(String userId, VideoView videoView) {
            this.userId = userId;
            this.videoView = videoView;
        }

        public void setPeerConnectionData(PeerConnectionData peerConnectionData) {
            this.peerConnectionData = peerConnectionData;
        }

        @Override
        public void onIceGatheringChange(RTCIceGatheringState state) {
            LOGGER.info("onIceGatheringChange: " + state.name());
            if(state == RTCIceGatheringState.COMPLETE) {
                peerConnectionData.setEndOfCandidates(true);
            }
        }

        @Override
        public void onIceCandidate(RTCIceCandidate rtcIceCandidate) {
            if (rtcIceCandidate.sdpMLineIndex > 0) return;
            LOGGER.info(String.format("onIceCandidate: sdp: %s, sdpMid: %s, sdpMLineIndex: %s, serverUrl: " +
                            "%s", rtcIceCandidate.sdp, rtcIceCandidate.sdpMid, rtcIceCandidate.sdpMLineIndex,
                    rtcIceCandidate.serverUrl));

            WebRtcP2pCandidateReq candidateReq = new WebRtcP2pCandidateReq();
            IceCandidate candidate = new IceCandidate();
            candidate.setCandidate(rtcIceCandidate.sdp);
            candidate.setSdpMid(rtcIceCandidate.sdpMid);
            candidate.setCandidate(rtcIceCandidate.sdp);
            candidate.setServerUrl(rtcIceCandidate.serverUrl);
            candidateReq.setIceCandidate(candidate);
            candidateReq.setRoomName(roomName);
            candidateReq.setToUserId(userId);
            rpcSignalService.iceCandidate(candidateReq);
        }

        @Override
        public void onTrack(RTCRtpTransceiver transceiver) {
            MediaStreamTrack track = transceiver.getReceiver().getTrack();
            String kind = track.getKind();
            LOGGER.info("[WebRtcClient on track], receiver: {}", kind);
//            setupVideoCodec(peerConnectionData.getPeerConnection());

            if (kind.equals(MediaStreamTrack.VIDEO_TRACK_KIND)) {
                VideoTrack videoTrack = (VideoTrack) track;
                videoTrack.addSink(frame -> {
                    if (videoView != null) {
                        try {
                            frame.retain();
                            videoView.setVideoFrame(frame);
                            frame.release();
                        } catch (Exception e) {
                            LOGGER.error("Render error", e);
                        }
                    }
                });
            }
        }

        @Override
        public void onIceConnectionChange(RTCIceConnectionState state) {
            if (state == RTCIceConnectionState.DISCONNECTED) {

            }
        }
    }

    class PeerConnectionData {
        private boolean endOfCandidates;
        private VideoView videoView;
        private RTCPeerConnection peerConnection;

        public PeerConnectionData(RTCPeerConnection peerConnection, VideoView videoView) {
            this.peerConnection = peerConnection;
            this.videoView = videoView;
        }

        public boolean isEndOfCandidates() {
            return endOfCandidates;
        }

        public void setEndOfCandidates(boolean endOfCandidates) {
            this.endOfCandidates = endOfCandidates;
        }

        public RTCPeerConnection getPeerConnection() {
            return peerConnection;
        }

        public void setPeerConnection(RTCPeerConnection peerConnection) {
            this.peerConnection = peerConnection;
        }

        public VideoView getVideoView() {
            return videoView;
        }
    }

    class OfferSdpCreateSessionDescriptionObserver implements CreateSessionDescriptionObserver {

        private String remoteUserId;
        private RTCPeerConnection rtcPeerConnection;

        public OfferSdpCreateSessionDescriptionObserver(String remoteUserId, RTCPeerConnection rtcPeerConnection) {
            this.remoteUserId = remoteUserId;
            this.rtcPeerConnection = rtcPeerConnection;
        }

        @Override
        public void onSuccess(RTCSessionDescription rtcSessionDescription) {
            String offerSdp = rtcSessionDescription.sdp;
            LOGGER.info("offerSdp: " + offerSdp);

            rtcPeerConnection.setLocalDescription(rtcSessionDescription, new SetSessionDescriptionObserver() {

                @Override
                public void onSuccess() {
                    LOGGER.info("offer setLocalDescription success!!!!!!!!!!!!!");

                    WebRtcP2pSdpReq sdpReq = new WebRtcP2pSdpReq();
                    sdpReq.setRoomName(roomName);
                    sdpReq.setToUserId(remoteUserId);
                    sdpReq.setSdp(rtcSessionDescription.sdp);
                    sdpReq.setSdpType(SdpType.offer);
                    rpcSignalService.sdp(sdpReq);
                }

                @Override
                public void onFailure(String error) {
                    LOGGER.error("offer setLocalDescription failure, error: " + error);

                }
            });
        }

        @Override
        public void onFailure(String error) {
            LOGGER.error("error: " + error);
        }
    }

    class AnswerSdpCreateSessionDescriptionObserver implements CreateSessionDescriptionObserver {

        private String remoteUserId;
        private RTCPeerConnection rtcPeerConnection;

        public AnswerSdpCreateSessionDescriptionObserver(String remoteUserId, RTCPeerConnection rtcPeerConnection) {
            this.remoteUserId = remoteUserId;
            this.rtcPeerConnection = rtcPeerConnection;
        }

        @Override
        public void onSuccess(RTCSessionDescription rtcSessionDescription) {
            String answerSdp = rtcSessionDescription.sdp;
            LOGGER.info("answerSdp: " + answerSdp);

            rtcPeerConnection.setLocalDescription(rtcSessionDescription, new SetSessionDescriptionObserver() {

                @Override
                public void onSuccess() {
                    LOGGER.info("answer setLocalDescription success!!!!!!!!!!!!!");
                }

                @Override
                public void onFailure(String error) {
                    LOGGER.error("answer setLocalDescription failure, error: " + error);
                }
            });

            WebRtcP2pSdpReq sdpReq = new WebRtcP2pSdpReq();
            sdpReq.setRoomName(roomName);
            sdpReq.setToUserId(remoteUserId);
            sdpReq.setSdp(rtcSessionDescription.sdp);
            sdpReq.setSdpType(SdpType.answer);
            rpcSignalService.sdp(sdpReq);
        }

        @Override
        public void onFailure(String error) {
            LOGGER.error("error: " + error);
        }
    }

    class RemoteSessionDescriptionObserver implements SetSessionDescriptionObserver {

        private String remoteUserId;
        private RTCPeerConnection rtcPeerConnection;

        public RemoteSessionDescriptionObserver(String remoteUserId, RTCPeerConnection rtcPeerConnection) {
            this.remoteUserId = remoteUserId;
            this.rtcPeerConnection = rtcPeerConnection;
        }

        @Override
        public void onSuccess() {
            rtcPeerConnection.createAnswer(new RTCAnswerOptions(), new AnswerSdpCreateSessionDescriptionObserver(remoteUserId, rtcPeerConnection));
        }

        @Override
        public void onFailure(String error) {
            LOGGER.error("error: " + error);
        }
    }

}
